Android通过AAPT(Android Asset Packing Tool)将各种资源集成打包并生成索引文件,即R文件。做模块化或编译优化相关工作时,需要对R文件有更多的了解,比如library module中的资源需要加resourcePrefix,做增量编译时可以只编译application module中的R文件等等。
application和library module中R文件的区别
结论:
- application module中的R文件的资源声明字段都是以常量形式存在,编译后使用到的资源字段以常量值的方式编译进class中。
- library module中的R文件的资源声明都是以变量形式存在,编译后使用到的字段会以代码引用方式编译进class中。
举个例子:
application module:
在application module中新建MainActivity,布局文件是activity_main.xml。编译后,application module的R文件中可以看到:
1 | public final class R { |
activity_main被final修饰,以常量形式存在。且编译后,这个field在class中的引用会被替换成常量值。
MainActivity.class中R.layout.activity_main会被替换成常量值0x7f09001c,十进制为2131296284。
1 | public class MainActivity extends AppCompatActivity { |
library module:
在library module中新建SubModuleActivity,布局文件是activity_sub_module.xml。编译后,library module中的R文件是:
1 | public final class R { |
activity_sub_module没有被final修饰,以变量形式存在。
编译后,SubModuleActivity.class中R.layout.activity_sub_module仍然以变量形式存在,以代码引用方式编译进class中。
1 | public class SubModuleActivity extends AppCompatActivity { |
试想下,如果library module中的R文件的资源声明也是以常量形式声明的话,代码中的资源引用会被替换成常量值。library module和application module中如果出现同名资源,根据R文件合并规则,该资源最终会以application中的资源为准,这就导致library module中代码里资源的常量值对应不到资源,报Resource NotFoundException。
module中R文件个数
一个module被编译时,会生成一个当前module的R文件,同时,该module依赖的module或aar也会在当前module生成R文件。这种依赖关系也包括跨层的传递依赖。所以,一个module中R文件的个数 = 其依赖的module/aar数量 + 1(自身的R文件)。
同理,application module中包含了整个app所有模块的R.java文件。
举个例子:
demo项目依赖结构:
1 | - app (pkg: com.tools.demo) |
其中library引用了fresco。
编译后,library模块中不仅包含了自身的R文件(com.tools.second.R),还包含了依赖的module(third模块)和依赖的aar(fresco)的R文件。
app模块则包含了整个app所有模块的R文件,包括直接依赖(library)和间接依赖(third, fresco)的所有module/aar的R文件。
资源合并规则
APK中的资源主要有3个来源:
- The main source set (generally located in src/main/res/)
- Build variant source sets
- Android libraries (AARs)
一个资源文件的文件名在resource type (anim/drawable/layout/menu..) 和 resource qualifier (比如drawable中的hdpi、value中的语言) 目录内是唯一的话,这个资源就被认为是唯一的。
资源文件冲突时,按照下面的优先级进行合并,低优的资源会被覆盖。
1 | build variant > build type > product flavor > main source set > library dependencies |
比如:main source set包含以下资源:
- res/layout/foo.xml
- res/layout-land/foo.xml
debug build type包含:
- res/layout/foo.xml
最终APK文件中的res/layout/foo.xml
来自debug build type,res/layout-land/foo.xml
来自main source set。
build variant:
build variant可以认为是build type和product flavor的结合。Gradle 会根据build type和product flavor自动创建build variant,并按照 <product-flavor><Build-Type>
的格式命名这些变体。
1 | android { |
根据上述配置,gradle会构建以下build variant:
- internalDebug
- internalRelease
- googlePlayDebug
- googlePlayRelease